#include <iostream>
#include <string>
#include <vector>
#include <fstream>
#include <unordered_set>
#include <set>
#include <algorithm>
#include <stdexcept>
#include <list>

struct Position {
    Position(const std::size_t r = 0, const std::size_t c = 0) : 
        row{r}, col{c} {}
    Position(const Position&) = default;
    Position& operator = (const Position&) = default;
    Position(Position&&) = default;
    Position& operator = (Position&&) = default;

    std::size_t row;
    std::size_t col;
};

auto operator == (const Position& p1, const Position& p2) -> bool {
    return (p1.row == p2.row) && (p1.col == p2.col);
}

struct PositionHash {
    auto operator () (const Position& pos) const -> std::size_t {
        auto hasher {std::hash<std::size_t>()};
        return hasher(pos.row) ^ hasher(pos.row);
    }
};

class Map {
public:
    Map (const std::string& filename) : _garden{}, _start{0, 0} {
        // Read garden
        std::ifstream infile {filename};   
        std::string line {};
        while (std::getline(infile, line))
            this->_garden.emplace_back( std::move(line) );

        // Get start
        for (std::size_t row = 0; row < this->_garden.size(); ++row)
        {
            std::size_t col {this->_garden[row].find('S')};
            if (col != std::string::npos)
            {
                this->_start = Position {row, col};
                break;
            }
        }
    }

public:
    auto walk(const std::size_t times) const -> std::size_t  {
        const std::size_t row_size {this->_garden.size()};
        const std::size_t col_size {this->_garden.front().size()};
        
        std::unordered_set<Position, PositionHash> rearched {this->_start};

        // Loop
        for (std::size_t time = 0; time < times; ++time) {
            std::list<Position> positions {};
            std::for_each(rearched.cbegin(), rearched.cend(), [&] (const Position& pos) {
                positions.emplace_back(pos);
            });
            rearched.clear();

            std::for_each(positions.cbegin(), positions.cend(), [&] (const Position& pos) {
                if (pos.col > 0) {
                    const Position left {pos.row, pos.col - 1};
                    if (this->is_garden_plot(left)) rearched.emplace(left);
                }

                if (pos.col < col_size - 1) {
                    const Position right {pos.row, pos.col + 1};
                    if (this->is_garden_plot(right)) rearched.emplace(right);
                }

                if (pos.row > 0) {
                    const Position up {pos.row - 1, pos.col};
                    if (this->is_garden_plot(up)) rearched.emplace(up);
                }
                if (pos.row < row_size - 1) {
                    const Position down {pos.row + 1, pos.col};
                    if (this->is_garden_plot(down)) rearched.emplace(down);
                }
            });
        }

        return rearched.size();
    }

public:
    auto is_garden_plot(const std::size_t row, const std::size_t col) const -> bool {
        return this->_garden[row][col] == '.' || this->_garden[row][col] == 'S';
    }

    auto is_garden_plot(const Position& pos) const -> bool {
        return this->is_garden_plot(pos.row, pos.col);
    }

    auto is_rocks(const std::size_t row, const std::size_t col) const -> bool {
        return this->_garden[row][col] == '#';
    }

    auto is_rocks(const Position& pos) const -> bool {
        return this->is_rocks(pos.row, pos.col);
    }

public:
    auto garden() const -> const std::vector<std::string>& {
        return this->_garden;
    }

    auto start() const -> const Position& {
        return this->_start;
    }
    
private:
    std::vector<std::string> _garden;
    Position _start;
};

auto part1() {
    Map map {"./src/day21/input.txt"};
    std::cout << map.walk(64) << std::endl;
}

auto main() -> int {
    part1();
    return 0;
}